-- PktAnalyser
-- Create by weism
-- 消息协议，负责消息的打包、解包

module("PktAnalyser", package.seeall);

-- 解包时的封装类型
local ET_NONE    = 0;
local ET_ARRAY   = 1;
local ET_MAPPING = 2;

-- 数据类型
local MT_INT8        = 0; -- "int8"
local MT_INT16       = 1; -- "int16"
local MT_INT32       = 2; -- "int32"
local MT_UINT8       = 3; -- "uint8"
local MT_UINT16      = 4; -- "uint16"
local MT_UINT32      = 5; -- "uint32"
local MT_STRING      = 6; -- "string"
local MT_STRING2     = 7; -- "string2"
local MT_STRING4     = 8; -- "string4"
local MT_BUFFER      = 9; -- "buffer"
local MT_BUFFER2     = 10; -- "buffer2"
local MT_BUFFER4     = 11; -- "buffer4"
local MT_MAP         = 12; -- "map"
local MT_MIXED       = 13; -- "mixed"
local MT_USER_UNDEFINED = 14; -- 用户自定义类型
local MT_ARRAY       = 20;

-- 消息编号和接口定义的映射关系
local pktInterfaceMap = {};

-- Def
local pktDefMap = {};
pktDefMap["int8"]    = { defName = "int8",    mType = MT_INT8,    eType = ET_NONE, };
pktDefMap["int16"]   = { defName = "int16",   mType = MT_INT16,   eType = ET_NONE, };
pktDefMap["int32"]   = { defName = "int32",   mType = MT_INT32,   eType = ET_NONE, };
pktDefMap["uint8"]   = { defName = "uint8",   mType = MT_UINT8,   eType = ET_NONE, };
pktDefMap["uint16"]  = { defName = "uint16",  mType = MT_UINT16,  eType = ET_NONE, };
pktDefMap["uint32"]  = { defName = "uint32",  mType = MT_UINT32,  eType = ET_NONE, };
pktDefMap["string"]  = { defName = "string",  mType = MT_STRING,  eType = ET_NONE, };
pktDefMap["string2"] = { defName = "string2", mType = MT_STRING2, eType = ET_NONE, };
pktDefMap["string4"] = { defName = "string4", mType = MT_STRING4, eType = ET_NONE, };
pktDefMap["buffer"]  = { defName = "buffer",  mType = MT_BUFFER,  eType = ET_NONE, };
pktDefMap["buffer2"] = { defName = "buffer2", mType = MT_BUFFER2, eType = ET_NONE, };
pktDefMap["buffer4"] = { defName = "buffer4", mType = MT_BUFFER4, eType = ET_NONE, };
pktDefMap["map"]     = { defName = "map",     mType = MT_MAP,     eType = ET_NONE, };
pktDefMap["mixed"]   = { defName = "mixed",   mType = MT_MIXED,   eType = ET_NONE, };

-- field
local pktFieldMap = {};

-- 字段编号
local pktIndexFieldMap = {};

-- 内部函数声明
local addDef;
local addField;
local addInterface;
local parseMembers;
local padUserDefinedValue;
local padDefValue;
local padMap;
local matchPktArgument;

-- 打包
function pack(msgNo, lpc)
    -- 获取该消息编号对应的用户类型
    local pktInterface = pktInterfaceMap[msgNo];
    if not pktInterface then error("消息不存在" .. msgNo) end

    -- 开始打包
    local buffer = Buffer.create(0);
    local offset = 1;
    padUserDefinedValue(buffer, offset, pktInterface.interfaceDef, lpc);
    return buffer;
end

-- 解包
function unpack(msgNo, buffer, offset)
    local pktInterface = pktInterfaceMap[msgNo];
    if not pktInterface then return nil end

    if not offset then offset = 1 end;
    local v, offset = matchPktArgument(pktInterface.interfaceDef, buffer, offset);
    return v;
end

-- 解析通讯文件
function loadCommDesc()
    local content = cc.FileUtils:getInstance():getStringFromFile("comm/communicate.txt");
    if string.sub(content, 1, 3) == "\x11\x12\x13" then
        content = cc.FileUtils:getInstance():getStringXorResult(string.sub(content, 4), 123);
    end

    local lines = string.explode(content, '\n')

    for _,line in pairs(lines) do
        line = string.trim(line);
        -- 过滤掉空行和注释行
        if line ~= "" and string.sub(line, 1, 1) ~= "#" and string.sub(line, 1, 1) ~= "[" then
            if string.startWith(line, "encap:") then
                -- 自定义类型
                addDef(line);
            elseif string.startWith(line, "void") then
                -- 接口定义
                addInterface(line);
            else
                -- 字段定义了
                addField(line);
            end
        end
    end
end

-- 添加一个类型定义
-- 例如：encap:array REQ_ATTR = string attrib, uint8 operator, int32 expect
addDef = function(content)
    local _, __, encapType, field, members = string.find(content, "encap:([%w]+)[ ]+([%w_]+)[ =]+(.*)");
    if not members then assert(false, "类型定义出错") end

    local pktDef = {
        defName = field,
        mType = MT_USER_UNDEFINED,
        members = {}
    };
    if encapType == "array" then
        pktDef.eType = ET_ARRAY;
    elseif encapType == "mapping" then
        pktDef.eType = ET_MAPPING;
    elseif encapType == "none" then
        pktDef.eType = ET_NONE;
    else
        assert(false, "错误的类型" .. encapType);
    end

    -- 解析参数列表
    pktDef.members = parseMembers(members);

    -- 记录下来
    pktDefMap[pktDef.defName] = pktDef;
end

-- 添加一个字段定义
-- 例如：string rid(77); // RID
addField = function(content)
    local _, __, type, name, index = string.find(content, "([^ ]+)[ ]+([^%(]+)%(([0-9]+)");
    local pktDef = pktDefMap[type];
    if not pktDef then
        error(string.format("不支持 %s 类型的字段！", type));
        return;
    end

    index = tonumber(index);
    local pktField = {
        index = index,
        name = name,
        desc = "",
        fieldDef = pktDef
    };

    pktFieldMap[name] = pktField;
    pktIndexFieldMap[index] = pktField;

    local fieldType = pktDef.mType;
    if pktDef.members then
        if #pktDef.members > 1 then
            error(string.format("不支持 %s 类型的字段！", type));
            return;
        end
        fieldType = pktDef.members[1].isArray and pktDef.members[1].pktDef.mType + MT_ARRAY or pktDef.members[1].pktDef.mType;
    end

    -- 通知 c++ 该字段定义
    add_map_define(name, index, fieldType);
end

-- 添加一个消息接口定义
-- 例如：void cmd_admin_clone(string class_id_name, int32 amount)
addInterface = function(content)
    local _, __, cmd, members = string.find(content, "void[ ]+([%w_]+)[%( ](.*)[%) ]");

    -- 根据消息名字得到消息编号
    local id = MSG.getMsgId(cmd);

    -- 得到interface的详细信息
    local pktInterface = {
        msgNo = id,
    }
    pktInterface.interfaceDef = {
        defName = "*",
        eType = ET_MAPPING,
        mType = MT_USER_UNDEFINED,
        members = parseMembers(members),
    }

    -- 记录下来
    pktInterfaceMap[pktInterface.msgNo] = pktInterface;

    local paraList = {};
    for i = 1, #pktInterface.interfaceDef.members do
        local paraInfo = pktInterface.interfaceDef.members[i];
        local paraType = paraInfo.isArray and paraInfo.pktDef.mType + MT_ARRAY or paraInfo.pktDef.mType;
        local para = {
            paraInfo.argName,
            paraType;
        };

        table.insert(paraList, para);
    end

    -- 传给 c++
    add_msg_define(pktInterface.msgNo, paraList);
end

-- 解析一串参数
parseMembers = function(members)
    local i = 1;
    local pktMember;
    local type;
    local m = {};
    for field in string.gfind(members, "[^ ,]+") do
        if pktMember == nil then
            -- 记录类型
            type = field;
            pktMember = { argName = nil, isOptional = false, isArray = false, pktDef = nil };
        else
            if string.sub(field, -2, -1) == "[]" then
                -- 封装成array了
                pktMember.isArray = true;
                pktMember.argName = string.sub(field, 1, -3);
                pktMember.pktDef = pktDefMap[type];
            else
                pktMember.argName = field;
                pktMember.pktDef = pktDefMap[type];
            end

            if not pktMember.pktDef or pktMember.pktDef.mType == MT_USER_UNDEFINED then
                error(string.format("不支持 %s 类型的字段！", type));
                return;
            end

            m[i] = pktMember;
            pktMember = nil;
            i = i + 1;
        end
    end

    return m;
end

-- 打包用户自定义字段
padUserDefinedValue = function(buffer, offset, pktDef, lpc)
    -- 逐个成员处理
    for i = 1, table.getn(pktDef.members) do
        local subValue = nil;
        if pktDef.eType == ET_ARRAY then
            -- 打包数组
            if i <= #lpc then
                subValue = lpc[i];
            else subValue = nil end
        elseif pktDef.eType == ET_MAPPING then
            -- 打包数值对
            subValue = lpc[pktDef.members[i].argName];
        else
            subValue = lpc;
        end

        if pktDef.members[i].isArray then
            -- 具备数组属性需要特殊处理下
            if is_array(subValue) then
                -- 值本来就是个数组了，按照数组打包
                Buffer.set16(buffer, offset, #subValue);
                offset = offset + 2;

                for j = 1, #subValue do
                    offset = padDefValue(buffer, offset, pktDef.members[i].pktDef, subValue[i]);
                end
            else
                -- 空的数组
                Buffer.set16(buffer, offset, 0);
                offset = offset + 2;
            end
        else
            -- 单值
            offset = padDefValue(buffer, offset, pktDef.members[i].pktDef, subValue);
        end
    end

    return offset;
end

-- 将一个值pack到缓冲区
padDefValue = function(buffer, offset, pktDef, value)
    if pktDef.mType == MT_INT8 or pktDef.mType == MT_UINT8 then
        Buffer.set8(buffer, offset, value);
        offset = offset + 1;
    elseif pktDef.mType == MT_INT16 or pktDef.mType == MT_UINT16 then
        Buffer.set16(buffer, offset, value);
        offset = offset + 2;
    elseif pktDef.mType == MT_INT32 or pktDef.mType == MT_UINT32 then
        Buffer.set32(buffer, offset, value);
        offset = offset + 4;
    elseif pktDef.mType == MT_STRING or pktDef.mType == MT_BUFFER then
        local len = string.len(value);
        Buffer.set8(buffer, offset, len);
        offset = offset + 1;

        Buffer.setstr(buffer, offset, string.len(value), value);
        offset = offset + string.len(value);
    elseif pktDef.mType == MT_STRING2 or pktDef.mType == MT_BUFFER2 then
        local len = string.len(value);
        Buffer.set16(buffer, offset, len);
        offset = offset + 2;

        Buffer.setstr(buffer, offset, string.len(value), value);
        offset = offset + string.len(value);
    elseif pktDef.mType == MT_STRING4 or pktDef.mType == MT_BUFFER4 then
        local len = string.len(value);
        Buffer.set32(buffer, offset, len);
        offset = offset + 4;

        Buffer.setstr(buffer, offset, string.len(value), value);
        offset = offset + string.len(value);
    elseif pktDef.mType == MT_MIXED then
        Buffer.set32(buffer, offset, 0);
        local len = LPC.saveBuffer(buffer, offset + 4, value);
        Buffer.set32(buffer, offset, len);
        offset = offset + 4 + len;
    elseif pktDef.mType == MT_MAP then
        offset = padMap(buffer, offset, value);
    elseif pktDef.mType == MT_USER_DEFINED then
        offset = padUserDefinedValue(buffer, offset, pktDef, value);
    end

    return offset;
end

-- 打包一个map
padMap = function(buffer, offset, value)
    -- 当前不知道有多少有效数据，暂时先填一个值占坑
    local old = offset;
    Buffer.set16(buffer, old, 0);

    -- 逐个字段打包
    local count = 0;
    for k, v in pairs(value) do
        -- 只处理key为字符串的情行
        if type(k) == "string" and pktFieldMap[k] ~= nil then
            local pktField = pktFieldMap[k];
            Buffer.set16(buffer, offset, pktField.index);
            offset = offset + 2;

            offset = padDefValue(buffer, offset, pktField.fieldDef, v);
            count = count + 1;
        end
    end

    -- 打包数量
    Buffer.set16(buffer, old, count);
    return offset;
end

-- 解析传入的网络包
matchPktArgument = function(pktDef, buffer, offset)
    local v;
    if pktDef.mType == MT_INT8 then
        v = Buffer.get8(buffer, offset);
        offset = offset + 1;
    elseif pktDef.mType == MT_INT16 then
        v = Buffer.get16(buffer, offset);
        offset = offset + 2;
    elseif pktDef.mType == MT_INT32 then
        v = Buffer.get32(buffer, offset);
        offset = offset + 4;
    elseif pktDef.mType == MT_UINT8 then
        v = Buffer.getu8(buffer, offset);
        offset = offset + 1;
    elseif pktDef.mType == MT_UINT16 then
        v = Buffer.getu16(buffer, offset);
        offset = offset + 2;
    elseif pktDef.mType == MT_UINT32 then
        v = Buffer.getu32(buffer, offset);
        offset = offset + 4;
    elseif pktDef.mType == MT_STRING or pktDef.mType == MT_BUFFER then
        local len = Buffer.getu8(buffer, offset);
        offset = offset + 1;
        v = Buffer.getstr(buffer, offset, len);
        offset = offset + len;
    elseif pktDef.mType == MT_STRING2 or pktDef.mType == MT_BUFFER2 then
        local len = Buffer.getu16(buffer, offset);
        offset = offset + 2;
        v = Buffer.getstr(buffer, offset, len);
        offset = offset + len;
    elseif pktDef.mType == MT_STRING4 or pktDef.mType == MT_BUFFER4 then
        local len = Buffer.getu32(buffer, offset);
        offset = offset + 4;
        v = Buffer.getstr(buffer, offset, len);
        offset = offset + len;
    elseif pktDef.mType == MT_MIXED then
        local len = Buffer.getu32(buffer, offset);
        offset = offset + 4;
        v, len = LPC.restoreBuffer(buffer, offset);
        offset = offset + len;
    elseif pktDef.mType == MT_MAP then
        v = {};
        local count = Buffer.getu16(buffer, offset);
        offset = offset + 2;
        for i = 1, count do
            -- key值
            local index = Buffer.get16(buffer, offset);
            offset = offset + 2;
            local pktField = pktIndexFieldMap[index];

            if pktField == nil then
                break;
            end

            local v2;
            v2, offset = matchPktArgument(pktField.fieldDef, buffer, offset);
            v[pktField.name] = v2;
        end
    elseif pktDef.mType == MT_USER_UNDEFINED then
        if pktDef.eType == ET_ARRAY then
            v = {};
        elseif pktDef.eType == ET_MAPPING then
            v = {};
        end

        -- 处理每一个成员
        for i = 1, table.getn(pktDef.members) do
            local subVal;
            if pktDef.members[i].isArray then
                -- 数组
                local len = Buffer.getu16(buffer, offset);
                offset = offset + 2;
                subVal = {};
                for j = 1, len do
                    local v2;
                    v2, offset = matchPktArgument(pktDef.members[i].pktDef, buffer, offset);
                    table.insert(subVal, v2);
                end
            else
                -- 单个值
                subVal, offset = matchPktArgument(pktDef.members[i].pktDef, buffer, offset);
            end

            -- 根据不同的封装类型，插入之
            if pktDef.eType == ET_ARRAY then
                table.insert(v, subVal);
            elseif pktDef.eType == ET_MAPPING then
                v[pktDef.members[i].argName] = subVal;
            else
                v = subVal;
            end
        end
    end

    return v, offset;
end

-- 单元测试
function test()
    -- CMD_LOGIN(string account, int32 auth_key, int32 seed)
    local buffer = pack(1, {
        account = "weism",
        auth_key = 123,
        seed = 456
    });
    assert(Buffer.toHex(buffer) == "05 77 65 69 73 6D 00 00 00 7B 00 00 01 C8", "测试失败");

    buffer = unpack(1, buffer);
    assert(buffer["account"] == "weism" and buffer.seed == 456, "测试失败");

    cclog("*** 协议包测试通过 *** ");
end

-- 内置的类型定义

_G["USE_CPP_PACKER"] = true;
